using System;
using System.Collections.Generic;
using System.Text;
using CodePlex.Resourceful;
using CodePlex.SpaceBlock.FS;
using System.Net;
using System.IO;
using System.Threading;

namespace CodePlex.SpaceBlock.JobModel
{
    public class TransferJob : BaseJob,  IJob
    {



        private class FileInfo
        {
            public IFileItem FromFile;
            public Path ToPath;
            public NameValuePairs Metadata;
        }


        private class FolderInfo
        {
            public IFileSystem From;
            public Path FromPath;
            public Path ToPath;

        }


        private readonly List<object> _items;
       

        private Operation _callback;

        public TransferJob(IFileSystem to)
            :  base(to)
        {
        
            _items = new List<object>();
        }

        public Operation Callback
        {
            get
            {
                return _callback;
            }
            set
            {
                _callback = value;
            }
        }


      
        public void AddFolder(IFileSystem from, Path fromFolder, Path toFolder)
        {
            FolderInfo fi = new FolderInfo();
            fi.From = from;
            fi.FromPath = fromFolder;
            fi.ToPath = toFolder;
            _items.Add(fi);
        }

        public void AddFile(IFileItem fromFile, Path toPath, NameValuePairs metadata)
        {

            FileInfo fi = new FileInfo();
            fi.FromFile = fromFile;
            fi.ToPath = toPath;
            fi.Metadata = metadata;
            _items.Add(fi);
        }


        private long _bytesTransferred;
        private long _bytesTotal;

        public void Execute()
        {


            IFileSystem to = this.TargetFS;
            bool toIsS3 = (to is S3FileSystem);
            ISupportsTransferWithMetadata toSupportsMetadata = to as ISupportsTransferWithMetadata;






            DateTime timeStart = DateTime.Now;
            long bytesTransferred = 0L;
            _bytesTotal = 0L;

            Utils.ForEach(_items, delegate(object item)
            {
                FileInfo info = item as FileInfo;
                if (info != null)
                {
                    _bytesTotal += info.FromFile.Size;
                }
            });

            bool done = false;
            Action<long> updateProgress = delegate(long current)
            {

                string prefix = "Transferring...";
                if (done)
                {
                    prefix = "Transfer complete.";
                }
                         


                long accurate = bytesTransferred + current;

                double pct = 0D;

                if (_bytesTotal > 0)
                {
                    pct = (double)accurate / (double)_bytesTotal;
                }

                // some fs may xfer more bytes!
                pct = Math.Min(pct, 1D);

                this.UpdateProgress((int)(pct * (double)this.MaxProgress));

                TimeSpan elapsed = DateTime.Now - timeStart;

                double bps = (double)(accurate) / elapsed.TotalSeconds;

                if (bps < 0 || double.IsNaN(bps))
                {
                    bps = 0;
                }
              
                this.UpdateCaption(prefix);
                if (bps == 0)
                {
                    this.Speed("");
                }
                else
                {
                    this.Speed("{0}/s", Utils.ComputeSizeString((long)bps));
                }
                this.Amount("{0}/{1}",
                    Utils.ComputeSizeString(accurate),
                    Utils.ComputeSizeString(_bytesTotal));
                _bytesTransferred = accurate;



                if (done)
                {
                    pct = 1;
                    this.UpdateProgress(this.MaxProgress);
                }

                this.Title("{0:0%} complete to {1}.", pct, to.Caption);
                          
            };


            foreach (object item in _items)
            {
                FolderInfo toFolder = item as FolderInfo;
                if (toFolder != null)
                {
                    this.Processing(toFolder);
                    to.EnsureFolderExists(toFolder.ToPath);
                    continue;
                }

                FileInfo info = item as FileInfo;
                if (info == null)
                {
                    continue;
                }

                this.Processing(info);
                try
                {

                    if (toSupportsMetadata != null)
                    {
                        if (toIsS3)
                        {
                            // md5 (pre)
                            this.UpdateCaption("Calculating hash...");
                            info.Metadata[HttpHeader.ContentMD5] = Utils.ToBase64(info.FromFile.QuickMD5);
                        }
                        toSupportsMetadata.Transfer(info.FromFile, info.ToPath, info.Metadata, updateProgress);
                    }
                    else
                    {
                        to.Transfer(info.FromFile, info.ToPath, updateProgress);
                    }

                }
                catch (WebException ex)
                {
                    // hide exception box if user cancelled... (xp)
                    if (ex.Message.Contains("The request was canceled"))
                    {
                        return;
                    }
                    throw;
                }
                catch (IOException ex)
                {
                    // hide exception box if user cancelled... (vista)
                    if (ex.InnerException is ThreadAbortException)
                    {
                        return;
                    }
                    throw;
                }
                bytesTransferred += info.FromFile.Size;


            }
            done = true;
            updateProgress(0);

            if (_callback != null)
            {
                _callback();
            }


        }

        private void Processing(FolderInfo toFolder)
        {
            _fromString = string.Format("{1}  [{0}]", toFolder.From.Caption, toFolder.FromPath);
           // _toString = string.Format("{1}  [{0}]", _to.Caption, toFolder.ToPath);
            this.UpdateTargetPath(toFolder.ToPath);
        }


        private void Processing(FileInfo toFile)
        {
            _fromString = string.Format("{1}  [{0}]", toFile.FromFile.FileSystem.Caption, toFile.FromFile.Path);
          //  _toString = string.Format("{1}  [{0}]", _to.Caption, toFile.ToPath);
            this.UpdateTargetPath(toFile.ToPath);
        }



        private int MaxProgress
        {
            get
            {
                return 100;
            }
        }


      

        private string _speed;
        private string _amount;
        private string _title;

        private string _fromString;
       




        private void Speed(string message, params object[] parameters)
        {
            _speed = string.Format(message, parameters);
        }

        private void Amount(string message, params object[] parameters)
        {
            _amount = string.Format(message, parameters);
        }


        private void Title(string message, params object[] parameters)
        {
            _title = string.Format(message, parameters);
        }


      
        override public string GetDescription()
        {
            return string.Format("{0} {2}", _title,_speed,_amount);
        }


        public string GetFrom()
        {
            return _fromString;
        }


        public long? GetSize()
        {
            return _bytesTotal;
        }
        public long? GetTransferred()
        {
            return _bytesTransferred;
        }

        public string GetSpeed()
        {
            return _speed;
        }

      
    }
}
